#ifndef	ACL_DNS_INCLUDE_H
#define	ACL_DNS_INCLUDE_H

#ifdef	__cplusplus
extern "C" {
#endif

#include "../stdlib/acl_define.h"
#include "../stdlib/acl_htable.h"
#include "../stdlib/acl_cache2.h"
#ifdef  ACL_UNIX
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#include "../event/acl_events.h"
#include "../aio/acl_aio.h"
#include "acl_netdb.h"

/* DNS 查询时的错误码定义 */

#define	ACL_DNS_OK			0
#define	ACL_DNS_OK_CACHE		1
#define	ACL_DNS_ERR_FMT			-1
#define	ACL_DNS_ERR_SVR			-2
#define	ACL_DNS_ERR_NO_EXIST		-3
#define	ACL_DNS_ERR_NO_SUPPORT		-4
#define	ACL_DNS_ERR_DENY		-5
#define	ACL_DNS_ERR_YX			-6
#define	ACL_DNS_ERR_YXRR		-7
#define	ACL_DNS_ERR_NXRR		-8
#define	ACL_DNS_ERR_NO_AUTH		-9
#define	ACL_DNS_ERR_NOT_ZONE		-10
#define	ACL_DNS_ERR_UNPACK		-15
#define	ACL_DNS_ERR_TIMEOUT		-16
#define	ACL_DNS_ERR_EXIST		-17
#define	ACL_DNS_ERR_BUILD_REQ		-18

typedef struct ACL_DNS_ADDR {
	char  ip[64];			/* DNS 服务器地址 */
	unsigned short port;		/* DNS 服务器端口 */
	struct sockaddr_in addr;	/* DNS 地址 */
	int   addr_len;			/* addr 大小 */
	int   mask_length;		/* DNS 服务器所在网络的掩码长度(> 0 && < 32) */
	struct in_addr in;		/* addr 的网段地址 */
} ACL_DNS_ADDR;

typedef struct ACL_DNS {
	ACL_AIO *aio;			/* 异步IO句柄 */
	unsigned short qid;		/* 发送请求的ID标识号 */
	ACL_ASTREAM *astream;		/* 异步流 */

	ACL_ARRAY *groups;		/* 域名组列表 */
	ACL_ARRAY *dns_list;		/* DNS 服务器地址列表 */
	int   dns_idx;			/* 当前使用的 dns_list 数组下标 */
	ACL_DNS_ADDR addr_from;		/* 来源 DNS 地址 */
	ACL_HTABLE *lookup_table;	/* 查询对象表 */
	ACL_CACHE2 *dns_cache;		/* 用于缓存DNS查询结果 */
	int   timeout;			/* 每次查询的超时时间值(秒) */
	int   retry_limit;		/* 查询超时时重试的次数限制 */
	unsigned int flag;		/* 标志位 */
#define	ACL_DNS_FLAG_ALLOC		(1 << 0)	/* 该异步句柄是动态分配的 */
#define	ACL_DNS_FLAG_CHECK_DNS_IP	(1 << 1)	/* 检查DNS地址是否匹配 */
#define	ACL_DNS_FLAG_CHECK_DNS_NET	(1 << 2)	/* 检查DNS网络是否匹配 */

	/* 该函数指针用来避免动态加载库的访问地址不一致问题 */
	ACL_EVENT_NOTIFY_TIME lookup_timeout;
} ACL_DNS;

typedef struct ACL_DNS_REQ ACL_DNS_REQ;

/**
 * 初始化DNS异步查询对象结构
 * @param dns {ACL_DNS*} DNS异步查询句柄
 * @param aio {ACL_AIO*} 异步句柄
 * @param timeout {int} 每次DNS查询时的超时值
 */
ACL_API void acl_dns_init(ACL_DNS *dns, ACL_AIO *aio, int timeout);

/**
 * 创建一个DNS异步查询对象并同时进行初始化
 * @param aio {ACL_AIO*} 异步句柄
 * @param timeout {int} 每次DNS查询时的超时值
 * @return {ACL_DNS*} DNS异步查询句柄
 */
ACL_API ACL_DNS *acl_dns_create(ACL_AIO *aio, int timeout);

/**
 * 打开DNS缓存机制
 * @param dns {ACL_DNS*} DNS异步查询句柄
 * @param limit {int} DNS 缓存中最大缓存条目
 */
ACL_API void acl_dns_open_cache(ACL_DNS *dns, int limit);

/**
 * 添加一个DNS服务器地址
 * @param dns {ACL_DNS*} DNS异步查询句柄
 * @param dns_ip {const char*} DNS服务器IP地址
 * @param dns_port {unsigned short} DNS服务器端口
 * @param mask_length {int} DNS服务器所在的网段掩码长度(0 < && < 32)
 */
ACL_API void acl_dns_add_dns(ACL_DNS *dns, const char *dns_ip,
	unsigned short dns_port, int mask_length);
/**
 * 关闭异步查询句柄同时释放所有资源
 * @param dns {ACL_DNS*} DNS异步查询句柄
 */
ACL_API void acl_dns_close(ACL_DNS *dns);

/**
 * 设置标志位，检查DNS来源IP地址是否与目标地址相同，若不同则丢弃所读的
 * 数据包，主要是为了防止DNS查询时的UDP攻击
 * @param dns {ACL_DNS*} DNS异步查询句柄
 */
ACL_API void acl_dns_check_dns_ip(ACL_DNS *dns);

/**
 * 设置标志位，检查DNS来源IP所在网段是否与目标网段相同，若不同则丢弃
 * 所读的数据包，主要是为了防止DNS查询时的UDP攻击
 * @param dns {ACL_DNS*} DNS异步查询句柄
 */
ACL_API void acl_dns_check_dns_net(ACL_DNS *dns);

/**
 * 设置DNS查询超时时重试次数
 * @param dns {ACL_DNS*} DNS异步查询句柄
 * @param retry_limit {int} 重试次数
 */
ACL_API void acl_dns_set_retry_limit(ACL_DNS *dns, int retry_limit);

/**
 * 异步查询一个域所对应的A记录IP地址集合
 * @param dns {ACL_DNS*} DNS异步查询句柄
 * @param domain {const char*} 域名
 * @param callback {void (*)(ACL_DNS_DB*, void*)} 查询成功或失败的回调函数,
 *  若返回给 callback 的 ACL_DNS_DB 为空则表示查询失败, 第二个参数为用户设置
 *  的参数, 第三个参数为查询失败时的错误号
 * @param ctx {void*} callback 的参数之一
 * @return {ACL_DNS_REQ*} 返回本次DNS查询的事件对象, 若为NULL则表示出错
 */
ACL_API ACL_DNS_REQ *acl_dns_lookup(ACL_DNS *dns, const char *domain,
	void (*callback)(ACL_DNS_DB *, void *, int), void *ctx);

/**
 * 向DNS查询对象中添加静态主机信息
 * @param dns {ACL_DNS*} DNS异步查询句柄
 * @param domain {const char*} 域名
 * @param ip_list {const char*} IP地址列表，分隔符为 ';'，如: 192.168.0.1;192.168.0.2
 */
ACL_API void acl_dns_add_host(ACL_DNS *dns, const char *domain, const char *ip_list);

/**
 * 向DNS查询对象中添加查询域名组信息
 * @param dns {ACL_DNS*} DNS异步查询句柄
 * @param group {const char*} 域名组名，如: .test.com, 则 a.test.com, b.test.com
 *  都属于 .test.com 域名组
 * @param ip_list {const char*} 如果非空则采用静态方式添加IP地址列表
 * @param refer {const char*} 域名组的代表域名, 将会用此域名代表整个域名组去做DNS查询
 * @param excepts {ACL_ARGV*} 虽然这些域名属于 group 的子域名但却不属于其域名组的
 *  成员集合
 */
ACL_API void acl_dns_add_group(ACL_DNS *dns, const char *group, const char *refer,
		const char *ip_list, const char *excepts);
/**
 * 取消某个查询事件对象
 * @param handle {ACL_DNS_REQ*} 某次域名查询事件
 */
ACL_API void acl_dns_cancel(ACL_DNS_REQ *handle);

/**
 * 根据错误号得到错误描述信息
 * @param errnum {int} DNS查询时返回的错误号
 * @return {const char*} 错误描述信息
 */
ACL_API const char *acl_dns_serror(int errnum);

#ifdef	__cplusplus
}
#endif

#endif

